//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.IO;
using System.Windows;
using OpenLS.Core;
using OpenLS.Spreadsheet.Internals;

namespace OpenLS.Spreadsheet.Formats.Biff.Records
{
    internal class XFRecord : WorkbookLevelRecord
    {
        private CellXF CellXF;
        private ushort _fontId;
        private bool _isLocked;
        private ushort _numberFormatId;
        private bool _isHidden;
        internal XFRecordType _type;
        internal int _parentXfId;


        private ushort ABCDAndParent
        {
            get
            {
                ushort result = 0;
                if (_isLocked)
                    result |= 0x01;
                if (_isHidden)
                    result |= 0x02;
                if (_type == XFRecordType.Style)
                    result |= 0x04;
                if (_f123Prefix)
                    result |= 0x08;
                result |= (ushort) (_parentXfId << 4);
                return result;
            }
        }

        internal override BiffRecordType RecordType
        {
            get { return BiffRecordType.XF; }
        }

        internal override void WriteContent(BiffWriterWorkbookContext context)
        {
            Contract.Requires(_type == XFRecordType.Cell || _type == XFRecordType.Style);
            BinaryWriter writer = context.Writer;
            WriteContentToWriter(writer);
            context.XFRecords.Add(this);
        }

        private void WriteContentToWriter(BinaryWriter writer)
        {
            writer.Write(FontId);
            //2
            writer.Write(_numberFormatId);
            //4
            writer.Write(ABCDAndParent);
            //6
            CellXF.Write(writer, ExtRecord != null);
        }

        public ushort FontId
        {
            get
            {
                Contract.Ensures(Contract.Result<ushort>() != 4);
                return _fontId;
            }
            set
            {
                Contract.Requires(value != 4);
                _fontId = value;
            }
        }

        internal FormatUsedAttributes Flags;
        private bool _f123Prefix;


        protected override void ReadContent(BiffReaderContext context, int length)
        {
            Contract.Ensures(_type == XFRecordType.Cell || _type == XFRecordType.Style);
            FontId = context.ReadUInt16();
            Contract.Assert(FontId != 4);
            _numberFormatId = context.ReadUInt16();
            ushort abcdAndParent = context.ReadUInt16();
            _isLocked = (abcdAndParent & 0x01) != 0;
            _isHidden = (abcdAndParent & 0x02) != 0;
            _type = ((abcdAndParent & 0x04) != 0) ? XFRecordType.Style : XFRecordType.Cell;
            _f123Prefix = (abcdAndParent & 0x08) != 0;
            _parentXfId = (abcdAndParent & 0xFFF0) >> 4;
            Contract.Assert(ABCDAndParent == abcdAndParent);
            CellXF.Read(context.Reader);
            context.XFRecords.Add(this);
        }


        internal FormattingRecord ToFormattingRecord(BiffReaderWorkbookContext context) //\\rename
        {
            Contract.Requires(_type == XFRecordType.Cell || _type == XFRecordType.Style);
            FormattingRecord result;
            switch (_type)
            {
                case XFRecordType.Cell:

                    result = new CellFormattingRecord();
                    break;
                case XFRecordType.Style:
                    result = new StyleFormattingRecord();
                    break;
                default:
                    throw new NotSupportedException();
            }
            if (_numberFormatId <= 163)
                result.Format = StandardFormats.GetFormat(_numberFormatId);
            else
            {
                string format;
                if (context.FormatTable.TryGetValue(_numberFormatId, out format))
                {
                    result.Format = Format.Parse(format);
                }
            }
            var extendedGradient = GetExtendedPropertyValue<XFExtGradient>(ExtPropType.GradientFill);
            if (extendedGradient != null)
            {
                result.Background = extendedGradient.ToFill();
            }
            else if (CellXF._backgroundPattern != BackgroundPattern.None)
            {
                SpreadsheetColor backgroundColor = context.GetColor(CellXF.icvBack, IcvXF.DefaultBackground);
                SpreadsheetColor foregroundColor = context.GetColor(CellXF.IcvFore, IcvXF.DefaultForeground);
                if (ExtRecord != null)
                {
                    var propertyValue = ExtRecord.GetPropertyValue<FullColorExt>(ExtPropType.BackgroundColor);
                    if (propertyValue != null)
                        backgroundColor = propertyValue.ToColor();
                    propertyValue = ExtRecord.GetPropertyValue<FullColorExt>(ExtPropType.ForegroundColor);
                    if (propertyValue != null)
                        foregroundColor = propertyValue.ToColor();
                }
                result.Background = new PatternFill(foregroundColor, backgroundColor,
                                                    ConvertFromBiff(CellXF._backgroundPattern));
            }
            else
                result.Background = new PatternFill(PatternType.None);

            if (CellXF.alcV != null)
                result.VerticalAlignment = CellXF.alcV;
            result.HorizontalAlignment = CellXF.alc;
            if (CellXF.fWrap)
                result.TextWrapping = TextWrapping.Wrap;
           // int fid = FontId;
           /* Contract.Assert(fid != 4);
            if (fid > 4)
                fid--;*/
            Font f = context.GetFont(FontId);
            result.Font = f;
            if (hasBorders)
            {
                Borders borders = new Borders();
                borders.Right = CreateBorder(CellXF.dgRight, CellXF.icvRight, ExtPropType.RightBorderColor, context);
                borders.Left = CreateBorder(CellXF.dgLeft, CellXF.icvLeft, ExtPropType.LeftBorderColor, context);
                borders.Top = CreateBorder(CellXF.dgTop, CellXF.icvTop, ExtPropType.TopBorderColor, context);
                borders.Bottom = CreateBorder(CellXF.dgBottom, CellXF.icvBottom, ExtPropType.BottomBorderColor, context);
                borders.Diagonal = CreateBorder(CellXF.dgDiag, CellXF.icvDiag, ExtPropType.DiagonalBorderColor, context);
                result.Borders = borders;
            }
            var extendedCellTextColor = GetExtendedPropertyValue<FullColorExt>(ExtPropType.CellTextColor);
            if (extendedCellTextColor != null)
                result.SetCellValue(CellProperty.Foreground, extendedCellTextColor.ToColor());
            var extendedFontScheme = GetExtendedPropertyValue<IntExt>(ExtPropType.FontScheme);
            if (extendedFontScheme != null)
            {
                var fontScheme = (FontSchemeType) extendedFontScheme.Value;
                switch (fontScheme)
                {
                    case FontSchemeType.None:
                        break;
                    case FontSchemeType.Major:
                        result.FontFamily = context.Workbook.Theme.FontScheme.MajorFont.Latin;
                        break;
                    case FontSchemeType.Minor:
                        result.FontFamily = context.Workbook.Theme.FontScheme.MinorFont.Latin;
                        break;
                    default:
                        throw new NotSupportedException();
                }
            }
            var extendedIndentation = GetExtendedPropertyValue<IntExt>(ExtPropType.Indentation);
            if (extendedIndentation != null)
            {
                result.Indentation = extendedIndentation.Value;
            }
            if (_type == XFRecordType.Cell && _parentXfId >0 && _parentXfId != 0xFFF)
            {
                CellFormattingRecord cellFormattingRecord = (CellFormattingRecord) result;
                cellFormattingRecord.SetParent( context.GetStyleFormattingRecord(_parentXfId));
                cellFormattingRecord.ApplyForParent();
            }
            return result;
        }

        public T GetExtendedPropertyValue<T>(ExtPropType propertyId) where T : ExtPropValueBase
        {
            if (ExtRecord == null)
                return null;
            return ExtRecord.GetPropertyValue<T>(propertyId);
        }

        private Border CreateBorder(BorderStyle style, IcvXF color, ExtPropType extendedProperty,
                                    BiffReaderWorkbookContext context)
        {
            SpreadsheetColor spreadsheetColor;
            var extendedColor = GetExtendedPropertyValue<FullColorExt>(extendedProperty);
            if (extendedColor != null)
            {
                spreadsheetColor = extendedColor.ToColor();
            }
            else
            {
                spreadsheetColor = style != BorderStyle.None
                                       ? context.GetColor(color)
                                       : context.GetColor(color, IcvXF.NoBorder);
            }
            if (style == BorderStyle.None && spreadsheetColor == null)
                return null;
            return new Border(style, spreadsheetColor);
        }

        private bool hasBorders
        {
            get { return CellXF.fAtrBdr || Enumerable.Any(GetBorderStyles(), s => s != BorderStyle.None); }
        }

        private IEnumerable<BorderStyle> GetBorderStyles()
        {
            yield return CellXF.dgBottom;
            yield return CellXF.dgDiag;
            yield return CellXF.dgLeft;
            yield return CellXF.dgRight;
            yield return CellXF.dgTop;
        }

#if DEBUG
        private static int count;
        internal FormattingRecord origin;
#endif

        private XfExtRecord _extRecord;

        public XfExtRecord ExtRecord
        {
            get { return _extRecord; }
            set
            {
                Contract.Assert((value != null));
                _extRecord = value;
                _extRecord.XFRecord = this;
            }
        }
#if DEBUG
        private static int counter = 0;
#endif
        internal static XFRecord FromFormattingRecord(FormattingRecord record, BiffWriterWorkbookContext context)
        {
            Contract.Requires(record != null);
            Contract.Requires(context != null);
            Contract.Ensures(Contract.Result<XFRecord>()._type == XFRecordType.Cell ||
                             Contract.Result<XFRecord>()._type == XFRecordType.Style);
            var result = new XFRecord();
            XFRecordType type = (record is CellFormattingRecord) ? XFRecordType.Cell : XFRecordType.Style;
#if DEBUG
            result.origin = record;
            //  Contract.Assert(!context.GetRecords(type).Contains(record));
            context.GetRecords(type).Add(record);
#endif
            result.CellXF.fAtrFnt = record.ApplyFont;
            result.CellXF.fAtrBdr = record.ApplyBorder ||
                                    record.Parent == null && record.IsCellFormatting && record.Borders != null;
                //\\check is cell record
            result.CellXF.fAtrAlc = record.ApplyAlignment;
            result.CellXF._fAtrNum = record.ApplyNumberFormat;
            result.CellXF.fAtrPat = record.ApplyFill;
            result.CellXF.fAtrProt = record.ApplyProtection;
            result._type = type;
            result._numberFormatId = context.GetExistingCodeId(record.FormatCode);
            if (record.IsLocked)
                result._isLocked = record.IsLocked;
            if (record.Background == null)
            {
            }
            else if (record.Background is PatternFill)
            {
                var patternFill = (PatternFill) record.Background; //\\
                result.CellXF.icvBack = context.GetColor(patternFill.BackgroundColor, IcvXF.DefaultBackground);
                result.CellXF.IcvFore = context.GetColor(patternFill.ForegroundColor, IcvXF.DefaultForeground);
#if DEBUG
                TestConverters();
#endif
                result.CellXF._backgroundPattern = ConvertToBiff(patternFill.Type);
            }
            else if (record.Background is GradientFill)
            {
            }
            else
            {
                // Contract.Assert(false);
                //\\  result.CellXF._backgroundPattern = BackgroundPattern.Solid;
            }
            if (record.VerticalAlignment != null)
                result.CellXF.alcV = record.VerticalAlignment;
            if (record.HorizontalAlignment != null)
            {
                result.CellXF.alc = record.HorizontalAlignment.GetValueOrDefault(HorizontalCellAlignment.General);
            }
            else
            {
                result.CellXF.alc = HorizontalCellAlignment.General;
            }
            result.CellXF.fWrap = record.TextWrapping != TextWrapping.NoWrap;
            if (record.Font != null)
            {
                ushort f = context.GetFontId(record.Font);
                if (f == 4)
                    f++;
                result.FontId = f;
            }
            if (record.Borders != null)
            {
                //\\  result.CellXF.fAtrBdr = true; //\\ xrtype == XFRecordType.Cell;
                Borders borders = record.Borders;
                IndexedColor color;
                SetBorder(context, borders, b => b.Left, s => result.CellXF.dgLeft = s, c => result.CellXF.icvLeft = c);
                SetBorder(context, borders, b => b.Right, s => result.CellXF.dgRight = s,
                          c => result.CellXF.icvRight = c);
                SetBorder(context, borders, b => b.Top, s => result.CellXF.dgTop = s, c => result.CellXF.icvTop = c);
                SetBorder(context, borders, b => b.Bottom, s => result.CellXF.dgBottom = s,
                          c => result.CellXF.icvBottom = c);
                SetBorder(context, borders, b => b.Diagonal, s => result.CellXF.dgDiag = s,
                          c => result.CellXF.icvDiag = c);
                //\\up and down
            }
            var properties = new List<ExtProp>(GetExtendedProperties(record, context.Workbook));
            if (properties.Count > 0)
            {
                result.ExtRecord = new XfExtRecord(properties);
                result.ExtRecord.XFRecord = result;
            }
            if (record is CellFormattingRecord)
            {
                if (record.Parent != null)
                {
                    var parent = record.Parent;
                    result._parentXfId = context.StyleStyleRecordTable[parent];

                }
            }
            else
                result._parentXfId = 0xFFF;
            return result;
        }

        private static IEnumerable<ExtProp> GetExtendedProperties(FormattingRecord record, Workbook workbook)
        {
            Contract.Assert(record != null);
            if (record.Background is GradientFill)
            {
                var gradientFill = (GradientFill) record.Background;
                if (gradientFill.Stops.Count > 0)
                    yield return new ExtProp(ExtPropType.GradientFill, new XFExtGradient(gradientFill));
            }
            if (record.Background is PatternFill)
            {
                var patternFill = (PatternFill) record.Background;
                if (FullColorExt.CanCreate(patternFill.BackgroundColor))
                    yield return
                        new ExtProp(ExtPropType.BackgroundColor, FullColorExt.Create(patternFill.BackgroundColor));
                if (FullColorExt.CanCreate(patternFill.ForegroundColor))
                    yield return
                        new ExtProp(ExtPropType.ForegroundColor, FullColorExt.Create(patternFill.ForegroundColor));
            }
            if (record.Foreground != null)
            {
                if (FullColorExt.CanCreate(record.Foreground))
                {
                    yield return new ExtProp(ExtPropType.CellTextColor, FullColorExt.Create(record.Foreground));
                }
            }
            if (record.Borders != null)
            {
                Borders borders = record.Borders;
                if (borders.Left != null && FullColorExt.CanCreate(borders.Left.Color))
                {
                    yield return new ExtProp(ExtPropType.LeftBorderColor, FullColorExt.Create(borders.Left.Color));
                }
                if (borders.Top != null && FullColorExt.CanCreate(borders.Top.Color))
                {
                    yield return new ExtProp(ExtPropType.TopBorderColor, FullColorExt.Create(borders.Top.Color));
                }
                if (borders.Right != null && FullColorExt.CanCreate(borders.Right.Color))
                {
                    yield return new ExtProp(ExtPropType.RightBorderColor, FullColorExt.Create(borders.Right.Color));
                }
                if (borders.Bottom != null && FullColorExt.CanCreate(borders.Bottom.Color))
                {
                    yield return new ExtProp(ExtPropType.BottomBorderColor, FullColorExt.Create(borders.Bottom.Color));
                }
                if (borders.Diagonal != null && FullColorExt.CanCreate(borders.Diagonal.Color))
                {
                    yield return
                        new ExtProp(ExtPropType.DiagonalBorderColor, FullColorExt.Create(borders.Diagonal.Color));
                }
            }
            if (workbook.Theme != null && workbook.Theme.FontScheme != null)
            {
                FontSchemeType type = FontSchemeType.None;
                if (record.FontFamily == workbook.Theme.FontScheme.MajorFont.Latin)
                    type = FontSchemeType.Major;
                else if (record.FontFamily == workbook.Theme.FontScheme.MinorFont.Latin)
                    type = FontSchemeType.Minor;
                if (type != FontSchemeType.None)
                    yield return
                        new ExtProp(ExtPropType.FontScheme, new IntExt((byte) type));
            }
        }

        private static void SetBorder(BiffWriterWorkbookContext context, Borders borders,
                                      Func<Borders, Border> borderAccessor, Action<BorderStyle> styleAction,
                                      Action<byte> colorAction)
        {
            Border b = borderAccessor(borders);
            if (b == null)
                return;
            if (b.Color != null)
            {
                colorAction((byte) context.GetColor(b.Color, IcvXF.NoBorder));
            }
            else
                colorAction(IcvXF.BlackBorder);
            styleAction(b.Style);
        }

        internal static BackgroundPattern ConvertToBiff(PatternType type)
        {
            Contract.Ensures(ConvertFromBiff(Contract.Result<BackgroundPattern>()) == type);
            switch (type)
            {
                case PatternType.None:
                    return BackgroundPattern.None;
                case PatternType.Solid:
                    return BackgroundPattern.Solid;
                case PatternType.Gray125:
                    return BackgroundPattern.Percent10;
                case PatternType.DarkDown:
                    return BackgroundPattern.ReverseDiagonalStripe;
                case PatternType.DarkGray:
                    return BackgroundPattern.Percent65;
                case PatternType.DarkGrid:
                    return BackgroundPattern.DiagonalCrosshatch;
                case PatternType.DarkHorizontal:
                    return (BackgroundPattern.HorizontalStripe);
                case PatternType.DarkTrellis:
                    return (BackgroundPattern.Percent75);
                case PatternType.DarkUp:
                    return (BackgroundPattern.DiagonalStripe);
                case PatternType.DarkVertical:
                    return (BackgroundPattern.VerticalStripe);
                case PatternType.Gray0625:
                    return (BackgroundPattern.Percent05);
                case PatternType.LightDown:
                    return (BackgroundPattern.LightDownwardDiagonal);
                case PatternType.LightGray:
                    return (BackgroundPattern.Percent25);
                case PatternType.LightGrid:
                    return (BackgroundPattern.Angle);
                case PatternType.LightHorizontal:
                    return (BackgroundPattern.Horizontal);
                case PatternType.LightTrellis:
                    return (BackgroundPattern.Percent60);
                case PatternType.LightUp:
                    return (BackgroundPattern.LightUpwardDiagonal);
                case PatternType.LightVertical:
                    return (BackgroundPattern.Vertical);
                case PatternType.MediumGray:
                    return (BackgroundPattern.Percent50);
                default:
                    throw new NotSupportedException();
            }
        }

        internal static PatternType ConvertFromBiff(BackgroundPattern pattern)
        {
            switch (pattern)
            {
                case BackgroundPattern.Solid:
                    return PatternType.Solid;
                case BackgroundPattern.None:
                    return PatternType.None;
                case BackgroundPattern.Vertical:
                    return PatternType.LightVertical;
                case BackgroundPattern.VerticalStripe:
                    return PatternType.DarkVertical;
                case BackgroundPattern.Horizontal:
                    return PatternType.LightHorizontal;
                case BackgroundPattern.HorizontalStripe:
                    return PatternType.DarkHorizontal;
                case BackgroundPattern.LightDownwardDiagonal:
                    return PatternType.LightDown;
                case BackgroundPattern.LightUpwardDiagonal:
                    return PatternType.LightUp;
                case BackgroundPattern.Percent75:
                    return PatternType.DarkTrellis;
                case BackgroundPattern.DiagonalCrosshatch:
                    return PatternType.DarkGrid;
                case BackgroundPattern.DiagonalStripe:
                    return PatternType.DarkUp;
                case BackgroundPattern.Percent10:
                    return PatternType.Gray125;
                case BackgroundPattern.Percent05:
                    return PatternType.Gray0625;
                case BackgroundPattern.Percent25:
                    return PatternType.LightGray;
                case BackgroundPattern.Percent50:
                    return PatternType.MediumGray;
                case BackgroundPattern.Percent60:
                    return PatternType.LightTrellis;
                case BackgroundPattern.Percent65:
                    return PatternType.DarkGray;
                case BackgroundPattern.Angle:
                    return PatternType.LightGrid;
                case BackgroundPattern.ReverseDiagonalStripe:
                    return PatternType.DarkDown;
                default:
                    throw new NotSupportedException();
            }
        }

#if DEBUG
        private static void TestConverters()
        {
            foreach (PatternType type in Enum.GetValues(typeof (PatternType)))
            {
                BackgroundPattern v = ConvertToBiff(type);
            }
        }
#endif


        public override void Apply(BiffReaderWorkbookContext ctx)
        {
            FormattingRecord sr = ToFormattingRecord(ctx);
            if (sr is CellFormattingRecord)
            {
                sr = ctx.Workbook.StyleSheet.LookupNewCellFormattingRecord((CellFormattingRecord) sr);
                ctx.AddCellFormattingRecord((CellFormattingRecord) sr);
            }
            else
            {
                ctx.AddStyleFormattingRecord((StyleFormattingRecord) sr);
            }
//            ctx.RecordTable.Add(ctx.RecordTable.Count, sr);
            //\\ ctx.Workbook.StyleSheet.StyleRecords.Add(sr);
        }


        public IEnumerable<byte> GetBytes()
        {
            var result = new byte[0x14];
            var memoryStream = new MemoryStream(result);
            using (var writer = new BinaryWriter(memoryStream))
            {
                WriteContentToWriter(writer);
                writer.Flush();
                Contract.Assert(result.Length == memoryStream.Position);
            }

            return result;
        }
    }
}